/*
 * $Id: socket.c,v 1.37 2006-02-08 11:04:01 bacon Exp $
 */

#include <xp/net/socket.h>
#include <xp/bas/memory.h>
#include <xp/bas/sysapi.h>
#include <xp/bas/errno.h>

/* TODO: replace fcntl by xp_fcntl */

static xp_ssize_t __recv_socket (xp_sckhnd_t hnd,
	void* buf, xp_size_t len, int flags, xp_time_t tmout, int opt_all);
static xp_ssize_t __send_socket (xp_sckhnd_t hnd,
	const void* msg, xp_size_t len, int flags, xp_time_t tmout, int opt_all);

#define TMOUT_ENABLED(t) ((t) >= 0)

int xp_sckstartup (void)
{
#ifdef _WIN32
	int major = 2, minor = 2;
	WORD version;
	WSADATA data;
 
	version = MAKEWORD(major, minor);
	if (WSAStartup(version, &data) != 0) {
		xp_seterrno (xp_maperrno_wsa(WSAGetLastError()));
		return -1;
	}
 
	if (LOBYTE(data.wVersion) != major ||
	    HIBYTE(data.wVersion) != minor) {
		xp_seterrno (xp_maperrno_wsa(WSAGetLastError()));
		WSACleanup ();
		return -1;
	}
#endif
	return 0;
}

int xp_sckcleanup (void)
{
#ifdef _WIN32
	if (WSACleanup() != 0) {
		xp_seterrno (xp_maperrno_wsa(WSAGetLastError()));
		return -1;
	}
#endif
	return 0;
}

xp_sckhnd_t xp_sckopen (int domain, int type, int protocol)
{
	xp_sckhnd_t n;

	n = socket (domain, type, protocol);
	if (n == XP_SCKHND_INVALID) {
#ifdef _WIN32
		xp_seterrno (xp_maperrno_wsa(WSAGetLastError()));
#endif
		return XP_SCKHND_INVALID;
	}

	return n;
}

int xp_sckclose (xp_sckhnd_t hnd)
{
#ifdef _WIN32
	if (closesocket(hnd) == -1) {
		xp_seterrno (xp_maperrno_wsa(WSAGetLastError()));
		return -1;
	}
	return 0;
#else
	int n;
	do {
		n = xp_close (hnd);
	} while (n == -1 && xp_errno == XP_EINTR);
	return n;
#endif
}

int xp_sckshtdwn (xp_sckhnd_t hnd, int how)
{
#ifdef _WIN32
	if (shutdown(hnd, how) == -1) {
		xp_seterrno (xp_maperrno_wsa(WSAGetLastError()));
		return -1;
	}
	return 0;	
#else
	return shutdown (hnd, how);
#endif
}

int xp_sckconn (
	xp_sckhnd_t hnd, const xp_sckadr_t* addr, 
	xp_scklen_t len, xp_time_t tmout)
{
	int n;
#ifdef _WIN32
	unsigned long cmd;
#else
	int saved = 0;
#endif

	if (TMOUT_ENABLED(tmout)) {
#ifdef _WIN32
		cmd = 1;
		if (ioctlsocket(hnd, FIONBIO, &cmd) == -1) {
			xp_seterrno (xp_maperrno_wsa(WSAGetLastError()));
			return -1;
		}
#else
		saved = fcntl (hnd, F_GETFL, 0);
		if (saved == -1) return -1;
		if (fcntl (hnd, F_SETFL, saved | O_NONBLOCK) == -1) return -1;
#endif
	}

#ifdef _WIN32
	n = connect (hnd, addr, len);
#else
	do {
		n = connect (hnd, addr, len);
	} while (n == -1 && xp_errno == XP_EINTR);
#endif

	if (TMOUT_ENABLED(tmout)) {
		int ret, err;
		xp_scklen_t len2;
		xp_ssize_t nn;

		/*xp_poll_t pp = { hnd, XP_POLLOUT };*/
		xp_poll_t pp;
		pp.fd = hnd;
		pp.events = XP_POLLOUT;

#ifdef _WIN32
		err = WSAGetLastError (); cmd = 0;
		if (ioctlsocket (hnd, FIONBIO, &cmd) == -1) return -1;
		if (n == -1 && err != WSAEWOULDBLOCK) {
			xp_seterrno (xp_maperrno_wsa(err));
			return -1;
		}
#else
		err = xp_errno;
		if (fcntl(hnd, F_SETFL, saved) == -1) return -1;
		if (n == -1 && err != XP_EINPROGRESS) {
			xp_seterrno (err);
			return -1;
		}
#endif

#ifdef _WIN32
		nn = xp_poll (&pp, 1, tmout);
#else
		do {
			nn = xp_poll (&pp, 1, tmout);
		} while  (nn == -1 && xp_errno == XP_EINTR);
#endif
		if (nn == -1) return -1; 
		if (nn == 0) {
			xp_seterrno (XP_ETIMEDOUT);
			return -1;
		}
		if ((pp.revents & XP_POLLOUT) == 0) {
			xp_seterrno (XP_EINVAL);
			return -1;
		}

		len2 = (xp_scklen_t)xp_sizeof (ret);
		if (getsockopt (hnd,
			SOL_SOCKET, SO_ERROR, (char*)&ret, &len2) == -1) return -1;

#ifdef _WIN32
		if (ret == WSAETIMEDOUT) {
#else
		if (ret == XP_ETIMEDOUT) {
#endif
			xp_seterrno (XP_ETIMEDOUT);
			return -1;
		}
		else if (ret != 0) {
#ifdef _WIN32
			xp_seterrno (xp_maperrno_wsa(ret));
#else
			xp_seterrno (ret);
#endif
			return -1;
		}

		n = 0;
	}

	return n;
}

int xp_sckconnx (
	xp_sckhnd_t hnd, const xp_sckadr_t* addr, xp_scklen_t len, 
	xp_time_t tmout, xp_bool_t (*cancel) (void*), void* arg)
{
	int n;
#ifdef _WIN32
	unsigned long cmd;
#else
	int saved = 0;
#endif

	if (TMOUT_ENABLED(tmout)) {
#ifdef _WIN32
		cmd = 1;
		if (ioctlsocket(hnd, FIONBIO, &cmd) == -1) {
			xp_seterrno (xp_maperrno_wsa(WSAGetLastError()));
			return -1;
		}
#else
		saved = fcntl (hnd, F_GETFL, 0);
		if (saved == -1) return -1;
		if (fcntl (hnd, F_SETFL, saved | O_NONBLOCK) == -1) return -1;
#endif
	}

#ifdef _WIN32
	n = connect (hnd, addr, len);
#else
	do {
		n = connect (hnd, addr, len);
	} while (n == -1 && xp_errno == XP_EINTR);
#endif

	if (TMOUT_ENABLED(tmout)) {
		int ret, err;
		xp_scklen_t len2;
		xp_ssize_t nn;

		/*xp_poll_t pp = { hnd, XP_POLLOUT };*/
		xp_poll_t pp;
		pp.fd = hnd;
		pp.events = XP_POLLOUT;

#ifdef _WIN32
		err = WSAGetLastError (); cmd = 0;
		if (ioctlsocket (hnd, FIONBIO, &cmd) == -1) return -1;
		if (n == -1 && err != WSAEWOULDBLOCK) {
			xp_seterrno (xp_maperrno_wsa(err));
			return -1;
		}
#else
		err = xp_errno;
		if (fcntl(hnd, F_SETFL, saved) == -1) return -1;
		if (n == -1 && err != XP_EINPROGRESS) {
			xp_seterrno (err);
			return -1;
		}
#endif

		if (cancel == NULL) {
#ifdef _WIN32
			nn = xp_poll (&pp, 1, tmout);
#else
			do {
				nn = xp_poll (&pp, 1, tmout);
			} while  (nn == -1 && xp_errno == XP_EINTR);
#endif
		}
		else {
			xp_time_t elapsed = 0;

			while (1) {
				elapsed += 1000;
#ifdef _WIN32
				nn = xp_poll (&pp, 1, 1000);
#else
				do {
					nn = xp_poll (&pp, 1, 1000);
				} while  (nn == -1 && xp_errno == XP_EINTR);
#endif
				if (nn == 0 && elapsed < tmout) {
					if (cancel(arg) == xp_true) {
						xp_seterrno (XP_ECANCELED);
						return -1; 
					}
					continue;
				}
				break;
			}
		}

		if (nn == -1) return -1; 
		if (nn == 0) {
			xp_seterrno (XP_ETIMEDOUT);
			return -1;
		}
		if ((pp.revents & XP_POLLOUT) == 0) {
			xp_seterrno (XP_EINVAL);
			return -1;
		}

		len2 = (xp_scklen_t)xp_sizeof (ret);
		if (getsockopt (hnd,
			SOL_SOCKET, SO_ERROR, (char*)&ret, &len2) == -1) return -1;

#ifdef _WIN32
		if (ret == WSAETIMEDOUT) {
#else
		if (ret == XP_ETIMEDOUT) {
#endif
			xp_seterrno (XP_ETIMEDOUT);
			return -1;
		}
		else if (ret != 0) {
#ifdef _WIN32
			xp_seterrno (xp_maperrno_wsa(ret));
#else
			xp_seterrno (ret);
#endif
			return -1;
		}

		n = 0;
	}

	return n;
}

int xp_sckbind (xp_sckhnd_t hnd, xp_sckadr_t* addr, xp_scklen_t len)
{
	int n;

#ifdef _WIN32
	n = bind (hnd, addr, len);
	if (n == -1) {
		xp_seterrno (xp_maperrno_wsa(WSAGetLastError()));
	}
#else
	do {
		n = bind (hnd, addr, len);
	} while (n == -1 && xp_errno == XP_EINTR);
#endif

	return n;
}

int xp_scklstn (xp_sckhnd_t hnd, int backlog)
{
#ifdef _WIN32
	if (listen(hnd, backlog) == -1) {
		xp_seterrno (xp_maperrno_wsa(WSAGetLastError()));
		return -1;
	}
	return 0;
#else
	return listen (hnd, backlog);
#endif
}

xp_sckhnd_t xp_sckacpt (xp_sckhnd_t hnd, 
	xp_sckadr_t* addr, xp_scklen_t* len, xp_time_t tmout)
{
	xp_ssize_t nn;
	xp_sckhnd_t hnd2;

	if (TMOUT_ENABLED(tmout)) {
		/*xp_poll_t pp = { hnd, XP_POLLIN };*/
		xp_poll_t pp;
		pp.fd = hnd;
		pp.events = XP_POLLIN;

#ifdef _WIN32
		nn = xp_poll (&pp, 1, tmout);
#else
		do {
			nn = xp_poll (&pp, 1, tmout);
		} while  (nn == -1 && xp_errno == XP_EINTR);
#endif
		if (nn == -1) return -1;
		if (nn == 0) {
			xp_seterrno (XP_ETIMEDOUT);
			return -1;
		}

		if ((pp.revents & XP_POLLIN) == 0) {
			xp_seterrno (XP_EINVAL);
			return -1;
		}
	}

#ifdef _WIN32
	hnd2 = accept (hnd, addr, len);
	if (hnd2 == XP_SCKHND_INVALID) {
		xp_seterrno (xp_maperrno_wsa(WSAGetLastError()));
	}
#else
	do {
		hnd2 = accept (hnd, addr, len);
	} while (hnd2 == XP_SCKHND_INVALID && xp_errno == XP_EINTR);
#endif

	return hnd2;
}


xp_ssize_t xp_sckrecv (xp_sckhnd_t hnd,
	void* buf, xp_size_t len, int flags, xp_time_t tmout)
{
	return __recv_socket (hnd, buf, len, flags, tmout, 0);
}

xp_ssize_t xp_sckrecvall (xp_sckhnd_t hnd,
	void* buf, xp_size_t len, int flags, xp_time_t tmout)
{
	return __recv_socket (hnd, buf, len, flags, tmout, 1);
}

xp_ssize_t xp_scksend (xp_sckhnd_t hnd,
	const void* msg, xp_size_t len, int flags, xp_time_t tmout)
{
	return __send_socket (hnd, msg, len, flags, tmout, 0);
}

xp_ssize_t xp_scksendall (xp_sckhnd_t hnd,
	const void* msg, xp_size_t len, int flags, xp_time_t tmout)
{
	return __send_socket (hnd, msg, len, flags, tmout, 1);
}

xp_ssize_t xp_sckrecvfrom (
	xp_sckhnd_t hnd, void* buf, xp_size_t size, int flags,
	xp_sckadr_t* from, xp_scklen_t* fromlen, xp_time_t tmout)
{
	xp_ssize_t nn;
		
	if (TMOUT_ENABLED(tmout)) {
		/*xp_poll_t pp = { hnd, XP_POLLIN };*/
		xp_poll_t pp;
		pp.fd = hnd;
		pp.events = XP_POLLIN;

#ifdef _WIN32
		nn = xp_poll (&pp, 1, tmout);
#else
		do {
			nn = xp_poll (&pp, 1, tmout);
		} while (nn == -1 && xp_errno == XP_EINTR);
#endif

		if (nn == -1) return -1;
		if (nn == 0) {
			xp_seterrno (XP_ETIMEDOUT);
			return -1;
		}

		if ((pp.revents & XP_POLLIN) == 0) {
			xp_seterrno (XP_EINVAL);
			return -1;
		}
	}
		
#ifdef _WIN32
	nn = recvfrom (hnd, (char*)buf, size, flags, from, fromlen);
	if (nn == -1) {
		xp_seterrno (xp_maperrno_wsa(WSAGetLastError()));
	}
#else
	do {
		nn = recvfrom (hnd, buf, size, flags, from, fromlen);
	} while (nn == -1 && xp_errno == XP_EINTR);
#endif

	return nn;
}

xp_ssize_t xp_scksendto (
	xp_sckhnd_t hnd, const void* msg, xp_size_t len, int flags,
	const xp_sckadr_t* to, xp_scklen_t tolen, xp_time_t tmout)
{
	xp_ssize_t nn;
		
	if (TMOUT_ENABLED(tmout)) {
		/*xp_poll_t pp = { hnd, XP_POLLOUT };*/
		xp_poll_t pp;
		pp.fd = hnd;
		pp.events = XP_POLLOUT;

#ifdef _WIN32
		nn = xp_poll (&pp, 1, tmout);
#else
		do {
			nn = xp_poll (&pp, 1, tmout);
		} while (nn == -1 && xp_errno == XP_EINTR);
#endif
		if (nn == -1) return -1;
		if (nn == 0) {
			xp_seterrno (XP_ETIMEDOUT);
			return -1;
		}

		if ((pp.revents & XP_POLLOUT) == 0) {
			xp_seterrno (XP_EINVAL);
			return -1;
		}
	}
		
#ifdef _WIN32
	nn = sendto (hnd, (char*)msg, len, flags, to, tolen);
	if (nn == -1) {
		xp_seterrno (xp_maperrno_wsa(WSAGetLastError()));
	}
#else
	do {
		nn = sendto (hnd, msg, len, flags, to, tolen);
	} while (nn == -1 && xp_errno == XP_EINTR);
#endif
		
	return nn;
}

static xp_ssize_t __recv_socket (xp_sckhnd_t hnd,
	void* buf, xp_size_t len, int flags, xp_time_t tmout, int opt_all)
{
	xp_ssize_t nn;
	xp_size_t yet = len;	
	char* p = (char*)buf;

	while (yet > 0) {
		if (TMOUT_ENABLED(tmout)) {
			/*xp_poll_t pp = { hnd, XP_POLLIN };*/
			xp_poll_t pp;
			pp.fd = hnd;
			pp.events = XP_POLLIN;

#ifdef _WIN32
			nn = xp_poll (&pp, 1, tmout);
#else
			do {
				nn = xp_poll (&pp, 1, tmout);
			} while (nn == -1 && xp_errno == XP_EINTR);
#endif
			if (nn == -1) return -1;

			if (nn == 0) {
				xp_seterrno (XP_ETIMEDOUT);
				return -1;
			}

			if ((pp.revents & XP_POLLIN) == 0) {
				xp_seterrno (XP_EINVAL);
				return -1;
			}
		}

#ifdef _WIN32
		nn = recv (hnd, p, yet, flags);
#else
		do {
			nn = recv (hnd, p, yet, flags);
		} while (nn == -1 && xp_errno == XP_EINTR);
#endif

		if (nn == -1) {
#ifdef _WIN32
			xp_seterrno (xp_maperrno_wsa(WSAGetLastError()));
#endif
			return -1;
		}
		if (nn == 0) break; // the peer connection is closed

		yet -= nn; p += nn;
		if (!opt_all) break;
	}

	return len - yet;
}

static xp_ssize_t __send_socket (xp_sckhnd_t hnd,
	const void* msg, xp_size_t len, int flags, xp_time_t tmout, int opt_all)
{
	xp_ssize_t nn;
	xp_size_t yet = len;
	const char* p = (const char*)msg;

	while (yet > 0) {
		if (TMOUT_ENABLED(tmout)) {
			/*xp_poll_t pp = { hnd, XP_POLLOUT };*/
			xp_poll_t pp;
			pp.fd = hnd;
			pp.events = XP_POLLOUT;

#ifdef _WIN32
			nn = xp_poll (&pp, 1, tmout);
#else
			do {
				nn = xp_poll (&pp, 1, tmout);
			} while (nn == -1 && xp_errno == XP_EINTR);
#endif
 
			if (nn == -1) return -1;
			if (nn == 0) {
				xp_seterrno (XP_ETIMEDOUT);
				return -1;
			}

			if ((pp.revents & XP_POLLOUT) == 0) {
				xp_seterrno (XP_EINVAL);
				return -1;
			}
		}

#ifdef _WIN32
		nn = send (hnd, p, yet, flags);
#else
		do {
			nn = send (hnd, p, yet, flags);
		} while (nn == -1 && xp_errno == XP_EINTR);
#endif
 
		if (nn == -1) {
#ifdef _WIN32
			xp_seterrno (xp_maperrno_wsa(WSAGetLastError()));
#endif
			return -1;
		}
		if (nn == 0) break;
 
		yet -= nn; p += nn;
		if (!opt_all) break;
	}

	return len - yet;
}


int xp_sckgetopt (xp_sckhnd_t hnd, int level, 
    int optname, void* optval, xp_scklen_t* optlen)
{
#ifdef _WIN32
	if (getsockopt(hnd, level, optname, optval, optlen) == -1) {
		xp_seterrno (xp_maperrno_wsa(WSAGetLastError()));
		return -1;
	}
	return 0;
#else
	return getsockopt (hnd, level, optname, optval, optlen);
#endif
}

int xp_scksetopt (xp_sckhnd_t hnd, int level,
    int optname, const void* optval, xp_scklen_t optlen)
{
#ifdef _WIN32
	if (setsockopt(hnd, level, optname, optval, optlen) == -1) {
		xp_seterrno (xp_maperrno_wsa(WSAGetLastError()));
		return -1;
	}
	return 0;
#else
	return setsockopt (hnd, level, optname, optval, optlen);
#endif
}

int xp_sckgetself (xp_sckhnd_t hnd, xp_sckadr_t* name, xp_scklen_t* len)
{
#ifdef _WIN32
	if (getsockname(hnd, name, len) == -1) {
		xp_seterrno (xp_maperrno_wsa(WSAGetLastError()));
		return -1;
	}
	return 0;
#else
	return getsockname (hnd, name, len);
#endif
}

int xp_sckgetpeer (xp_sckhnd_t hnd, xp_sckadr_t* name, xp_scklen_t* len)
{
#ifdef _WIN32
	if (getpeername(hnd, name, len) == -1) {
		xp_seterrno (xp_maperrno_wsa(WSAGetLastError()));
		return -1;
	}
	return 0;
#else
	return getpeername (hnd, name, len);
#endif
}
